import { DynamicModule, Global, Module, Provider } from '@nestjs/common'
import {
  AuthModuleAsyncOptions, AuthModuleOptions, AuthModuleOptionsFactory,
} from './interfaces/auth-options.interface'
import { AUTH_OPTIONS } from './auth.constants'
import { JwtModule } from '@nestjs/jwt'
import { CryptographyService } from './services/cryptography.service'
import { DiscordAuthService } from './services/discord-auth.service'
import { AuthService } from './services/auth.service'
import { CaslAbilityFactory } from './casl/casl-ability.factory'

@Module({
  imports: [ JwtModule.register({}) ],
  providers: [
    CryptographyService,
    DiscordAuthService,
    AuthService,
    CaslAbilityFactory,
  ],
  exports: [ CryptographyService, DiscordAuthService, AuthService, CaslAbilityFactory, JwtModule, AUTH_OPTIONS ],
})
@Global()
export class AuthModule {
  public static forRoot(config: AuthModuleOptions): DynamicModule {
    return {
      module: AuthModule,
      providers: [
        {
          provide: AUTH_OPTIONS,
          useValue: config,
        },
      ],
    }
  }

  public static forRootAsync(options: AuthModuleAsyncOptions): DynamicModule {
    return {
      module: AuthModule,
      imports: options.imports,
      providers: this.createAsyncProviders(options),
    }
  }

  private static createAsyncProviders(options: AuthModuleAsyncOptions): Provider[] {
    if (options.useExisting || options.useFactory) {
      return [ this.createAsyncOptionsProvider(options) ]
    }

    return [
      this.createAsyncOptionsProvider(options),
      {
        useClass: options.useClass!,
        provide: options.useClass!,
      },
    ]
  }

  private static createAsyncOptionsProvider(options: AuthModuleAsyncOptions): Provider {
    if (options.useFactory) {
      return {
        provide: AUTH_OPTIONS,
        useFactory: options.useFactory,
        inject: options.inject || [],
      }
    }

    return {
      provide: AUTH_OPTIONS,
      useFactory: async (optionsFactory: AuthModuleOptionsFactory) => await optionsFactory.createAuthOptions(),
      inject: [ (options.useExisting || options.useClass)! ],
    }
  }
}
